package skeleton;

import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.util.Collections;
import java.util.Vector;

public class SkeletonMaker {
	public Vector<Point2D> crossPoints;
	public Vector<SkeletonEvent> eventList = new Vector<SkeletonEvent>();
	public Vector<Line2D>lines=new Vector<Line2D>();
	
	
	private HE getHEdgeRing(HEFactory heFactory, Vector<Point2D> poly) {
		Point2D n1 = (Point2D) poly.firstElement();
		Point2D n2 = (Point2D) poly.get(1);

		HE firstHEdge = heFactory.makeEdge((Point2D) n1, (Point2D) n2);
		HE lastHEdge = firstHEdge;

		for (int i = 2; i < poly.size(); i++) {
			n1 = (Point2D) poly.get(i - 1);
			n2 = (Point2D) poly.get(i);
			HE cHEdge = heFactory.makeEdge((Point2D) n1, (Point2D) n2);

			lastHEdge.getSym().connectOrgToEdge(cHEdge);
			lastHEdge = cHEdge;
		}

		HE cHEdge = heFactory.makeEdge((Point2D) poly.lastElement(),
				(Point2D) poly.firstElement());

		lastHEdge.getSym().connectOrgToEdge(cHEdge);
		firstHEdge.connectOrgToEdge(cHEdge.getSym());

		return firstHEdge;
	}
	public Vector<HE> getSkeleton(HEFactory heFactory, Vector<Point2D> poly) {
		System.out.println("getSkeleton: ");
		
		crossPoints = new Vector<Point2D>();
		lines=new Vector<Line2D>();

		Vector<HE> hEdges = new Vector<HE>();
		Vector<HE> frontHEdges = new Vector<HE>();
		OffsetHEdge startHEdge = (OffsetHEdge) getHEdgeRing(heFactory, poly);
		OffsetHEdge cHE = startHEdge;

		do {
			hEdges.add(cHE);
			cHE = (OffsetHEdge) cHE.getNext();
		} while (cHE != startHEdge);

		Vector<Point2D> points = new Vector<Point2D>();
		for (int i = 0; i < poly.size(); i++) {
			Point2D p = poly.get(i);
			points.add(new Point2D.Double(p.getX(), p.getY()));
		}

		for (int i = 0; i < hEdges.size(); i++) {
			int j = i + 1;
			if (j == hEdges.size())
				j = 0;
			OffsetHEdge cHEdge = (OffsetHEdge) hEdges.get(i);

			Point2D dest = cHEdge.getDest();
			Point2D org = cHEdge.getOrg();

			Point2D p1 = points.get(i);
			Point2D p2 = points.get(j);

			OffsetHEdge newHEdge1 = (OffsetHEdge) heFactory.makeEdge(dest, p2);
			OffsetHEdge newHEdge2 = (OffsetHEdge) heFactory.makeEdge(p2, p1);
			OffsetHEdge newHEdge3 = (OffsetHEdge) heFactory.makeEdge(p1, org);

			frontHEdges.add(newHEdge2.getSym());

			cHEdge.setNext(newHEdge1);
			newHEdge1.setNext(newHEdge2);
			newHEdge2.setNext(newHEdge3);
			newHEdge3.setNext(cHEdge);
		}
		;

		for (int i = 0; i < hEdges.size(); i++) {
			int j = i + 1;
			if (j == hEdges.size())
				j = 0;

			OffsetHEdge cHEdge = (OffsetHEdge) hEdges.get(i);
			OffsetHEdge nextHEdge = (OffsetHEdge) hEdges.get(j);

			cHEdge.getNext().setSym(nextHEdge.getNext().getNext().getNext());
			nextHEdge.getNext().getNext().getNext().setSym(cHEdge.getNext());

			cHEdge.getNext().getNext().getSym().setNext(
					nextHEdge.getNext().getNext().getSym());
		}

		eventList = this.refreshFrontEdges(frontHEdges);
		while (frontHEdges.size() > 0&&eventList.size()>0) {

			Collections.sort(eventList);

			Vector<SkeletonEvent> parallelEvents = new Vector<SkeletonEvent>();
			SkeletonEvent cEvent = eventList.firstElement();
			parallelEvents.add(cEvent);
			double ct = cEvent.getT();

			for (int j = 0; j < frontHEdges.size(); j++) {
				OffsetHEdge cH = (OffsetHEdge) frontHEdges.get(j);
				BiSector cB = cH.getBiSector();
				Point2D nP = cB.getP(ct);
				cH.getOrg().setLocation(nP);
			}
			
		

			BiSector bis = cEvent.getBiSector();
			HE oldFrontEdge = bis.getHEdge();
			if (cEvent.isConcave()) {
				System.out.println("concav: ");
				// delete frontEdge
				//HE prevFrontEdge = oldFrontEdge.getPrev();
				HE nextFrontEdge = oldFrontEdge.getNext();
				
				oldFrontEdge.melt();
				/*HE prevEdge2 = oldFrontEdge.getSym().getPrev();

				prevFrontEdge.setNext(nextFrontEdge);
				prevEdge2.setNext(oldFrontEdge.getSym().getNext());*/
				frontHEdges.remove(oldFrontEdge);
			
				this.offsetFrontEdge(heFactory, nextFrontEdge);
		

			} else if (!cEvent.isConcave()) {
				System.out.println("convex: ");
			
				HE oldFrontEdgePrev=oldFrontEdge.getPrev();
				Point2D p=cEvent.getP();
				oldFrontEdge.setOrg(cEvent.getP());
				
				
			/*	for (int i=0;i<frontHEdges.size();i++){
					HE cH=frontHEdges.get(i);
					
					if (p.distance(arg0))
					
				
				}*/
				HEdge oppositeEdge = (HEdge) cEvent.getEdgeToSplit();
				HEdge splitEdge = (HEdge) (heFactory.split(oppositeEdge,cEvent.getP()));
				oldFrontEdgePrev.setNext(splitEdge);
				oppositeEdge.setNext(oldFrontEdge);
				
				this.offsetFrontEdge(heFactory, oldFrontEdge);
				this.offsetFrontEdge(heFactory, splitEdge);
				
				frontHEdges.add(splitEdge);
				//return hEdges;
			}

			for (int j = 0; j < frontHEdges.size(); j++) {
				lines.add(frontHEdges.get(j).getLine2D());
			}

			frontHEdges=checkFrontEdges(frontHEdges);
			eventList = this.refreshFrontEdges(frontHEdges);
		
		}
		Vector<HE>unusedHE=new Vector<HE>();
		for (int i=0;i<hEdges.size();i++){
			HE cHe=hEdges.get(i);
			HE nextHE=cHe;
			do{
				if (nextHE.getLength()<0.0001){
					unusedHE.add(nextHE);
				}
				nextHE=nextHE.getNext();
			}while(cHe!=nextHE);
		}
		for (int i=0;i<unusedHE.size();i++){
			//System.out.println("melt");
			HE cHe=unusedHE.get(i);
			cHe.melt();
			unusedHE.remove(cHe);
			unusedHE.remove(cHe.getSym());
			i=0;
		}
		//System.out.println("frontHEdgesAfter: " + frontHEdges.size());
		return hEdges;
	}
	private SkeletonEvent getConcaveEvent(HE cHEdge) {
		OffsetHEdge hE1 = (OffsetHEdge) cHEdge;
		OffsetHEdge hE2 = (OffsetHEdge) hE1.getPrev();
		BiSector bs1 = hE1.getBiSector();
		BiSector bs2 = hE2.getBiSector();
		Point2D sP = VecMath2D.getRayRayIntersection(bs1.getOrg(),
				bs1.getDir(), bs2.getOrg(), bs2.getDir());
		if (sP != null) {
			SkeletonEvent event = new SkeletonEvent();
			double t = hE2.getOrg().distance(sP)
					/ hE2.getBiSector().getDir().distance(0, 0);
			event.setT(t);
			event.setP(sP);
			event.setBiSector(hE2.getBiSector());
			event.setConcave(true);
			return event;
			// eventList.add(event);
		}
		return null;
	}

	private Vector<SkeletonEvent> getConvexEvents(OffsetHEdge hEdge) {
		Vector<SkeletonEvent> eventList = new Vector<SkeletonEvent>();

		BiSector bisector = hEdge.getBiSector();
		double speed1 = VecMath2D.getLength(bisector.getDir());
		Point2D p0 = hEdge.getPrev().getOrg();
		Point2D p1 = hEdge.getOrg();
		Point2D p2 = hEdge.getDest();
		if (VecMath2D.relativeCCW2(p0, p1, p2) == VecMath2D.CWRIGHT) {
			OffsetHEdge nextHEdge = hEdge;
			do {
				BiSector bS1 = nextHEdge.getBiSector();
				BiSector bS2 = ((OffsetHEdge) nextHEdge.getNext())
						.getBiSector();
				
				if (nextHEdge != hEdge && nextHEdge != hEdge.getPrev()) {
					Point2D dir = VecMath2D.getFromAToB(nextHEdge.getOrg(),
							nextHEdge.getDest());
					Point2D crossPoint = VecMath2D.getRayLineIntersection(p1,
							bisector.getDir(), nextHEdge.getOrg(), dir);
					if (crossPoint != null) {
						// crossPoints.add(crossPoint);
						double d = nextHEdge.getLine2D().ptLineDist(p1);
						double totald = crossPoint.distance(p1);
						
						//System.out.println("d: "+d);
						//System.out.println("totald: "+totald);
						double speed2 = (totald) / d;
						
						double cT = totald / (speed1 + speed2);
						//double cT = totald / (speed1);

						Point2D tP = VecMath2D.addScaled(bisector.getOrg(),
								bisector.getDir(), cT);

						int ccw1 = VecMath2D.relativeCCW2(bS1.getOrg(), bS1
								.getDest(), tP);
						int ccw2 = VecMath2D.relativeCCW2(bS2
								.getOrg(),bS2.getDest(), tP);
						
					
						//crossPoints.add(tP);
						//if (isBetween())
						if (ccw1 != VecMath2D.CCWLEFT
								&& ccw2!= VecMath2D.CWRIGHT) {
							crossPoints.add(tP);
							SkeletonEvent event = new SkeletonEvent();
							event.setT(cT);
							event.setP(tP);
							event.setBiSector(bisector);
							event.setConcave(false);
							event.setEdgeToSplit(nextHEdge);
							eventList.add(event);

						}
					}
				}
				nextHEdge = (OffsetHEdge) nextHEdge.getNext();
			} while (nextHEdge != hEdge);
		}
		return eventList;
	}
	private Vector<SkeletonEvent> refreshFrontEdges(Vector<HE> frontHEdges) {
		refreshBisectors(frontHEdges);
		Vector<SkeletonEvent> eventList = new Vector<SkeletonEvent>();

		for (int i = 0; i < frontHEdges.size(); i++) {
			OffsetHEdge hE1 = (OffsetHEdge) frontHEdges.get(i);
			SkeletonEvent cEvent = this.getConcaveEvent(hE1);
			if (cEvent != null) {
				eventList.add(cEvent);
			}
		}
		for (int i = 0; i < frontHEdges.size(); i++) {
			OffsetHEdge hE1 = (OffsetHEdge) frontHEdges.get(i);
			Vector<SkeletonEvent> cEvents = this.getConvexEvents(hE1);
			eventList.addAll(cEvents);
		}
		return eventList;
	}
	private void offsetFrontEdge(HEFactory heFactory,HE frontEdge){
		Point2D newPoint=new Point2D.Double();
		newPoint.setLocation(frontEdge.getOrg());
		
		HE prevEdge=frontEdge.getPrev().getSym().getPrev();
		HE newHEdge=heFactory.makeEdge(newPoint,frontEdge.getOrg());
		
		newHEdge.getSym().setNext(frontEdge.getSym().getNext());
		frontEdge.getSym().setNext(newHEdge.getSym());
		
		newHEdge.setNext(prevEdge.getNext());
		prevEdge.setNext(newHEdge);
		
		//newHEdge.setOrgToAll(newPoint);
		//newHEdge.getSym().setOrgToAll(frontEdge.getOrg());
		newHEdge.setOrgToAll(newPoint);
		//newHEdge.getSym().setOrgToAll(frontEdge.getOrg());
	}
	private void refreshBisectors(Vector<HE> frontHEdges) {
		for (int i = 0; i < frontHEdges.size(); i++) {
			HE cHEdge = frontHEdges.get(i);
			HE prev = cHEdge.getPrev();
			Point2D a = prev.getOrg();
			Point2D b = cHEdge.getDest();
			Point2D p = cHEdge.getOrg();
			Point2D biSectorDir = VecMath2D.getBiSector(p, a, b);
			BiSector biSector = new BiSector();
			biSector.setDir(biSectorDir);
			biSector.setHEdge(cHEdge);
			((OffsetHEdge) cHEdge).setBiSector(biSector);
		}
	}
	
	private Vector<HE> checkFrontEdges(Vector<HE> frontHEdges) {

		System.out.println("checkFrontEdges");
		for (int i = 0; i < frontHEdges.size(); i++) {
			HE frontEdge = frontHEdges.get(i);
			
			if (frontEdge.getLength() < 0.00001) {
				frontEdge.melt();
				frontHEdges.remove(frontEdge);
				i=0;
			}
		}
		for (int i = 0; i < frontHEdges.size(); i++) {
			HE frontEdge = frontHEdges.get(i);
			
		
				if (frontEdge.getNext().getNext() == frontEdge) {
				//System.out.println("parallelLines");
				frontEdge.getSym().setSym(frontEdge.getNext().getSym());
				frontEdge.getNext().getSym().setSym(frontEdge.getSym());
				frontHEdges.remove(frontEdge);
				frontHEdges.remove(frontEdge.getNext());
				i=0;
			}
			
		}
	
		return frontHEdges;
	}
	}
